package sgs.skin;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import sgs.common.utils.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;

/**
 * 下载角色皮肤的类。
 * 该类负责根据版本和角色代码下载相应的皮肤图像。
 */
public class SkinDownloader {
    // 定义用户代理字符串，用于模拟浏览器访问，避免被服务器识别为自动化程序，防止HttpClient发送http请求时403 Forbidden和安全拦截
    private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36";
    // 同时下载任务的最大数量，用于控制并发下载。
    private static final int MAX_CONCURRENT_DOWNLOADS = 10;
    // 下载文件的保存目录
    private static String DIR_DOWNLOAD = Config.INSTANCE.getProperty("dir.download");

    public void downloadSkin() {
        downloadSkinForVersion(VersionEnum.ZN, 1, 6, 1, 15);
        downloadSkinForVersion(VersionEnum.OL, 0, 0, 0, 15);
    }

    /**
     * 根据版本、皮肤等级范围和皮肤编号范围下载皮肤。
     *
     * @param version    皮肤的版本。
     * @param minSkinLevel 最小皮肤等级。ZN=1，OL=0
     * @param maxSkinLevel 最大皮肤等级。ZN=6，OL=0
     * @param minSkinNum 最小皮肤编号。ZN=1，OL=0
     * @param maxSkinNum 最大皮肤编号。15
     */
    public void downloadSkinForVersion(VersionEnum version, int minSkinLevel, int maxSkinLevel, int minSkinNum, int maxSkinNum) {
        // 提取对应版本的角色代码列表。
        List<String> characterCodes = extractCharacterCodes(version);

        ExecutorService executorService = Executors.newFixedThreadPool(MAX_CONCURRENT_DOWNLOADS); // 限制线程池大小以控制并发数
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        try {
            // 遍历角色代码，为每个角色下载皮肤。
            for (String characterCode : characterCodes) {
                String urlBase = version.getUrl();
                String skinCode = DataCache.characterCodeToSkinCodeMap.get(version + "." + characterCode);
                if (skinCode != null) {
                    // 如果已经存在对应的皮肤代码映射，则直接下载该皮肤。
                    futures.add(downloadSkinAsync(executorService, urlBase, version, characterCode, skinCode, false));
                } else {
                    // 如果不存在对应的皮肤代码，则根据皮肤等级和编号范围下载皮肤。
                    for (int skinLevel = minSkinLevel; skinLevel <= maxSkinLevel; skinLevel++) {
                        for (int skinNum = minSkinNum; skinNum <= maxSkinNum; skinNum++) {
                            skinCode = VersionEnum.OL.equals(version) ? characterCode + String.format("%02d", skinNum) : skinLevel + characterCode + String.format("%02d", skinNum);
                            futures.add(downloadSkinAsync(executorService, urlBase, version, characterCode, skinCode, false));
                        }
                    }
                }
            }
            // 等待所有下载任务完成。
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
            Logger.log(version + " skins downloaded successfully.");
        } catch (Exception e) {
//            Thread.currentThread().interrupt();
            Logger.log(Level.SEVERE, "Error during skin download for version: " + version + ". " + e.getMessage(), e);
        } finally {
            executorService.shutdown(); // 关闭线程池
        }
    }
    /**
     * 下载大尺寸（高清）皮肤。
     * 根据已存在的皮肤代码，下载对应版本的高清皮肤。
     */
    public void downloadMaxSkin() {
        ExecutorService executorService = Executors.newFixedThreadPool(MAX_CONCURRENT_DOWNLOADS); // 限制线程池大小以控制并发数
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        try {
            // 遍历已存在的皮肤代码
            for (String existSkinCode : DataCache.existSkinCodes) {
                // 根据皮肤代码的前缀确定皮肤的版本
                VersionEnum version;
                if (existSkinCode.startsWith(VersionEnum.ZN.toString())) {
                    version = VersionEnum.ZN;
                } else if (existSkinCode.startsWith(VersionEnum.OL.toString())) {
                    version = VersionEnum.OL;
                } else {
                    continue;
                }
                String skinCode = existSkinCode.substring(3);
                // 根据皮肤代码和版本，获取对应的角色代码
                String characterCode = SkinFileProcessor.getChrctCodeBySkinCode(skinCode, version);
                // 发起异步下载任务，并将任务的未来对象添加到列表中
                futures.add(downloadSkinAsync(executorService, version.getMaxUrl(), version, characterCode, skinCode, true));
            }
            // 等待所有下载任务完成
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
            Logger.log("max skins downloaded successfully.");
        } catch (Exception e) {
            Logger.log(Level.SEVERE, "Error during max skin download. " + e.getMessage(), e);
        } finally {
            executorService.shutdown(); // 关闭线程池
        }
    }
    /**
     * 异步下载皮肤文件。<br/>
     *
     * 此方法通过提交一个异步任务到指定的执行服务中，来下载指定皮肤。任务会被安排在后台线程中执行，
     * 从而不会阻塞当前线程。这有利于提高应用程序的响应性和吞吐量，特别是在处理大量皮肤下载任务时。
     *
     * @param executorService 执行异步任务的线程池。它负责调度和执行下载皮肤的任务。
     * @param urlBase 皮肤资源的基础URL，用于构造皮肤资源的完整下载地址。
     * @param version 皮肤资源的版本，用于确定下载哪个版本的皮肤。
     * @param characterCode 角色代码，用于标识要下载皮肤的角色。
     * @param skinCode 皮肤代码，用于标识要下载的具体皮肤。
     * @param isMax 是否下载最大尺寸的皮肤。
     * @return 一个CompletableFuture对象，表示异步下载任务的完成。此任务不返回任何结果，因此返回类型为Void。
     */
    private CompletableFuture<Void> downloadSkinAsync(ExecutorService executorService, String urlBase, VersionEnum version, String characterCode, String skinCode, boolean isMax) {
        // 使用CompletableFuture.runAsync提交一个Runnable任务到指定的executorService中执行。
        // 这个Runnable任务负责调用downloadSkin方法来下载皮肤。
        return CompletableFuture.runAsync(() -> downloadSkin(urlBase, version, characterCode, skinCode, isMax), executorService);
    }

    /**
     * 下载角色皮肤。
     *
     * 根据给定的URL基础路径、版本、角色代码和皮肤代码，下载对应的皮肤图像。
     *
     * @param urlBase 皮肤图像URL的基础路径。
     * @param version 皮肤的版本。
     * @param characterCode 角色的代码。
     * @param skinCode 皮肤的代码。
     * @param isMax 指示是否下载最大尺寸皮肤。如果不是，则会检验该皮肤代码是否已经存在。
     */
    private void downloadSkin(String urlBase, VersionEnum version, String characterCode, String skinCode, boolean isMax) {
        // 如果不是下载大尺寸皮肤，则需校验皮肤代码是否已经存在。
        // 大尺寸的皮肤代码不校验，大尺寸皮肤代码来自于本地已存储的皮肤文件，进行全量下载
        if (!isMax && DataCache.existSkinCodes.contains(version + "." + skinCode)) {
            return;
        }

        try (CloseableHttpClient client = HttpClientUtils.getHttpClient()) {
            HttpGet httpget = new HttpGet();
            httpget.setHeader("User-Agent", USER_AGENT);
            httpget.setURI(URI.create(urlBase + skinCode + ".png"));

            try (CloseableHttpResponse response = client.execute(httpget)) {
                if (response.getStatusLine().getStatusCode() != 200) {
                    return;
                }

                HttpEntity entity = response.getEntity();
                if (entity != null && entity.getContentLength() > 1000) { // 大于1KB的图片
                    String skinDir = DataCache.skinNameToDirMap.getOrDefault(DataCache.characterCodeToNameMap.get(version + "." + characterCode), "未分类");
                    Path targetDirectory = Paths.get(DIR_DOWNLOAD, skinDir);
                    Files.createDirectories(targetDirectory);
                    String fileName = isMax ? version.toString().toLowerCase() + "." + skinCode + ".max.png" : version.toString().toLowerCase() + "." + skinCode + ".png";
                    File skinfile = targetDirectory.resolve(fileName).toFile();

                    try (InputStream is = entity.getContent();
                         FileOutputStream fileOutputStream = new FileOutputStream(skinfile)) {
                        byte[] buffer = new byte[10 * 1024];
                        int ch;
                        while ((ch = is.read(buffer)) != -1) {
                            fileOutputStream.write(buffer, 0, ch);
                        }
                    }
                }
            } catch (IOException e) {
                Logger.log(Level.SEVERE, "Error downloading skinCode: " + version + skinCode, e);
            }
        } catch (IOException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
            Logger.log(Level.SEVERE, "HttpClient setup error: " + e.getMessage(), e);
        }
    }

    /**
     * 根据指定的版本枚举，提取对应版本的角色代码列表。
     * 此方法遍历DataCache.characterCodeToNameMap，筛选出以指定版本开头的字符编码。
     * 如果角色代码的版本未知，则记录日志。
     *
     * @param version 版本枚举，用于筛选字符编码。
     * @return 返回符合指定版本条件的角色代码列表。
     */
    private List<String> extractCharacterCodes(VersionEnum version) {
        List<String> characterCodes = new ArrayList<>();
        for (String key : DataCache.characterCodeToNameMap.keySet()) {
            if (key.startsWith(version.toString())) {
                String characterCode = key.replace(version + ".", "");
                characterCodes.add(characterCode);
            } else {
                Logger.log("Unknown version: " + key);
            }
        }
        return characterCodes;
    }
}
